/*
 * Copyright (C) 2008 Joe Kovar III.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * Author: Joe Kovar III <mr.joebert@gmail.com>
 *
 */

int max (int a, int b){return a > b ? a : b;}
int min (int a, int b){return a < b ? a : b;}

enum
{
  COL_COLOR = 0,
  COL_HEX,
  COL_R,
  COL_G,
  COL_B,
  NUM_COLS
} ;

/* Alias the ALT mask */
#define GDK_ALT_MASK GDK_MOD1_MASK

/* Event deconfusion */
#define EVENT_HANDLED TRUE
#define EVENT_UNHANDLED FALSE

/*
	Disable any depreciated stuff we know about,
	since we're making a point to stay away from depreciated code
*/
#define G_DISABLE_DEPRECATED
#define GTK_DISABLE_DEPRECATED
#define GDK_DISABLE_DEPRECATED
#define GDK_PIXBUF_DISABLE_DEPRECATED

/* Fetch headers */
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)

/* Global references */
GtkWidget		*window, *swatch, *about_dialog, *preferences_dialog;
GtkImage		*magnified;
GtkEntry		*rgb, *hex;
GtkTreeView		*history_view;
GtkToggleButton	*aot_check, *cch_check;
GtkSpinButton	*hsize_spin;
GdkWindow		*root_window;
GdkPixmap		*buffer;
GdkPixbuf		*history_swatch, *eyedropper;
int				history_len, last_x, last_y, i, j, x, xx, y, yy, width, height, left, right, top, bottom, red, green, blue, _red, _green, _blue, _gray;
gboolean		running, color_locked, always_on_top, confirm_clear, redraw_forced;
cairo_t			*ctx, *swatch_ctx;

guint32			color;
GdkModifierType	state;
GdkImage		*section;

GtkListStore	*history_store;
GtkTreeIter		history_iter;

gchar* authors[] = {"Joe Kovar III <mr.joebert@gmail.com>", NULL };

/* BEGIN 3rd PARTY */
/*
 * getexename - Get the filename of the currently running executable
 *
 * The getexename() function copies an absolute filename of the currently 
 * running executable to the array pointed to by buf, which is of length size.
 *
 * If the filename would require a buffer longer than size elements, NULL is
 * returned, and errno is set to ERANGE; an application should check for this
 * error, and allocate a larger buffer if necessary.
 *
 * Return value:
 * NULL on failure, with errno set accordingly, and buf on success. The 
 * contents of the array pointed to by buf is undefined on error.
 *
 * Notes:
 * This function is tested on Linux only. It relies on information supplied by
 * the /proc file system.
 * The returned filename points to the final executable loaded by the execve()
 * system call. In the case of scripts, the filename points to the script 
 * handler, not to the script.
 * The filename returned points to the actual exectuable and not a symlink.
 *
 * written by Nicolai Haehnle <prefect_@gmx.net>
 */
char* getexename(char* buf, size_t size)
{
	char linkname[128]; /* /proc/<pid>/exe */
	pid_t pid;
	int ret;
	
	/* Get our PID and build the name of the link in /proc */
	pid = getpid();
	
	if (snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid) < 0)
	{
		return "";
	}

	
	/* Now read the symbolic link */
	ret = readlink(linkname, buf, size);
	
	/* In case of an error, leave the handling up to the caller */
	if (ret == -1)
		return NULL;
	
	/* Report insufficient buffer size */
	if (ret >= size)
	{
		return NULL;
	}
	
	/* Ensure proper NUL termination */
	buf[ret] = 0;
	
	return buf;
}

/**
 * A simple function that will strip out program name from the absolute path that 
 * points to the programe file.
 * 
 * Parameter buf : String buffer that will hold the final absolute path string.
 *                 If getexename failed to get part of the absolute path, this
 *                 variable will store only the filename.
 *
 * Parameter bufSize : size of string buffer.
 *
 * Parameter r_file : String that contains the filename.
 *
 * Parameter str_length : length of filename string.
 *
 * Hubert 2008/8/13
 **/
int getAbsolutePathForFile(char* buf, int bufSize, char* r_file, int str_length){
	char* exeFileName;	
	exeFileName = getexename(buf, bufSize);

	//bit wordy here, but I dont want to modify Nicolai's code
	if((exeFileName != NULL)&&(buf != NULL)){
		//find the last occurrence of /
		char* tmpLoc = strrchr(buf, '/');
		//cut off the program name
		*(tmpLoc+1) = 0;		
		strncat(buf, r_file, str_length);

		return 1; //success
	}else{
		//if failed to get the absolute path, copy filename into
		//buffer.
		int i;
		for(i=0;i <str_length;i++){
			*(buf+i) = *(r_file+i);
		}		
		*(buf+str_length) = 0;
		return 0; //fail
	}
}
/* END 3rd PARTY */

/*
	Shows an About dialog,
	connected to the Help->About menuitems activate signal
	NOTE: This DOES NOT get moved into a glade/xml interface file
*/
void show_about ()
{
	gtk_show_about_dialog (NULL,
		"authors", authors,
		"copyright", "© 2008, Joe Kovar III",
		"license", "GPL Version 2\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
		"website", "http://code.google.com/p/picksel/",
		"website-label", "Visit Picksel Website",
		NULL
	);
	return;
}

/*
	Saves persistant preferences
	TODO: Add color selection history
*/
void save_preferences ()
{
	gchar		*path, *kdata;
	gsize		size;
	GKeyFile	*prefs = g_key_file_new();

	/* If this is the first time running, make a preferences directory */
	path = g_build_filename (G_DIR_SEPARATOR_S, g_get_home_dir (), ".picksel", NULL);
	if (g_file_test (path, G_FILE_TEST_IS_DIR) == FALSE)
	{
		g_mkdir (path, 0700);
	}
	g_free (path);
	
	/* Let the gkeyfile format the config file */
	g_key_file_set_boolean (prefs, "preferences", "always_on_top", always_on_top);
	g_key_file_set_boolean (prefs, "preferences", "confirm_clear", confirm_clear);
	g_key_file_set_integer (prefs, "preferences", "history_len", history_len);

	kdata = g_key_file_to_data (prefs, &size, NULL);
	if (kdata) 
	{/* save to config.ini in the users home directory */
		path = g_build_filename (G_DIR_SEPARATOR_S, g_get_home_dir (), ".picksel", "config.ini", NULL);
		g_file_set_contents (path, kdata, (gint)size,  NULL);
	} 
	g_free (kdata);
	g_free (path);
	g_key_file_free (prefs);

	return;
}

/*
	Destroys the preferences window/dialog
*/
void hide_preferences ()
{
	gtk_widget_destroy (GTK_WIDGET (aot_check));
	gtk_widget_destroy (GTK_WIDGET (cch_check));
	gtk_widget_destroy (GTK_WIDGET (hsize_spin));
	gtk_widget_destroy (GTK_WIDGET (preferences_dialog));
	
	return;
}

/*
	Applies the preferences from the preferences window,
	then destroys the preferences window
*/
void apply_preferences ()
{
	always_on_top	= gtk_toggle_button_get_active ( aot_check);
	confirm_clear	= gtk_toggle_button_get_active ( cch_check);
	history_len		= gtk_spin_button_get_value_as_int ( hsize_spin);
	
	hide_preferences ();

	return;
}

/*
	Builds and displays the preferences window
*/
void show_preferences ()
{
	GtkBuilder	*builder;
	char		xmlfile[256];
	
	getAbsolutePathForFile(xmlfile, 256, "preferences-dialog.xml", 22);

	/* Build the interface */
	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, xmlfile, NULL);
	
	/* Reference widgets */
	preferences_dialog	= GTK_WIDGET (gtk_builder_get_object (builder, "preferences-dialog"));
	aot_check			= GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "aot-check"));
	cch_check			= GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "cch-check"));
	hsize_spin			= GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "hsize-spin"));
	
	/* Connect signals for cancel/apply buttons */
	gtk_builder_connect_signals (builder, NULL);
	g_object_unref (G_OBJECT (builder));
	
	/* Setup widget values from global settings */
	gtk_toggle_button_set_active (aot_check, always_on_top);
	gtk_toggle_button_set_active (cch_check, confirm_clear);
	gtk_spin_button_set_value (hsize_spin, history_len);

	/* Display the preferences window.dialog */
	gtk_widget_show (preferences_dialog);

	return;
}

/*
	Hooked to the checkbox in the clear confirm dialog
	disabled confirmation of clearing selection history untill it's turned back on via the preferences dialog
*/
void disable_clear_confirm ()
{
	confirm_clear = FALSE;
	
	return;
}

/*
	Shows a confirmation dialog asking if the selection history should really be cleared,
	connected to the File->Clear_History menuitems activate signal
*/
void clear_history ()
{
	GtkBuilder	*builder;
	GtkDialog	*dialog;
	char		xmlfile[256];
	
	getAbsolutePathForFile(xmlfile, 256, "clear-history-confirm-dialog.xml", 32);

	/* Build the interface */
	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, xmlfile, NULL);
	
	/* Reference widgets */
	dialog	= GTK_DIALOG (gtk_builder_get_object (builder, "clear-history-confirm-dialog"));
	
	/* Connect signals for cancel/apply buttons */
	gtk_builder_connect_signals (builder, NULL);
	g_object_unref (G_OBJECT (builder));

	gint result = !confirm_clear ? GTK_RESPONSE_YES : gtk_dialog_run (dialog);
	switch (result)
	{
		case GTK_RESPONSE_YES:
			gtk_list_store_clear (history_store);
		default:
			gtk_widget_destroy (GTK_WIDGET (dialog));
		break;
	}

	return;
}

/*
	Removes any selected items in the selction history
*/
void remove_history_selections ()
{
	GtkTreeSelection *selection = gtk_tree_view_get_selection ( history_view );
	GtkListStore *store;
	GtkTreeModel *model;

	if (gtk_tree_selection_count_selected_rows(selection) == 0)
	{
		return;
	}

	GList *list = gtk_tree_selection_get_selected_rows ( selection, &model );
	store = GTK_LIST_STORE ( model );

	int nRemoved = 0;
	while (list)
	{
		int ipath = atoi(gtk_tree_path_to_string (list->data));
		ipath -= nRemoved;
		GString *fixed_path = g_string_new("");
		g_string_printf(fixed_path, "%d", ipath);

		GtkTreePath *path = gtk_tree_path_new_from_string (fixed_path->str);
		g_string_free (fixed_path, TRUE);

		if (path)
		{
			if ( gtk_tree_model_get_iter ( model, &history_iter, path) )
			{
				gtk_list_store_remove ( store, &history_iter );
				nRemoved++;
			}
			gtk_tree_path_free (path);
		}
		list = list->next;
	}
	g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
	g_list_free (list);
}

/*
	Signal handler for the context menu items which copy their labels to the clipboard
*/
void copy_label_to_clipboard (GtkAction *action, gpointer userdata)
{
	gchar	*item_label;
	
	/* Fetch the label from the clicked menu item */
	g_object_get (G_OBJECT (action), "label", &item_label, NULL);
	gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), item_label, -1);

	g_free (item_label);
	return;
}

/*
	Actual history context-menu builder
*/
void popup_history_menu (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
{
	GtkBuilder		*builder;
	GtkAction		*action;
	GtkTreeModel	*model;
	int				i;
	gchar			*sred, *sgreen, *sblue, *shex, *item_label;
	gchar			*actions[8] = {	"hex-tocb", "color-tocb",
									"background-tocb", "background-color-tocb",
									"border-tocb", "border-color-tocb",
									"outline-tocb", "outline-color-tocb"};
	char		xmlfile[256];
	
	getAbsolutePathForFile(xmlfile, 256, "history-context-menu.xml", 24);
	
	/* Build the interface */
	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, xmlfile, NULL);

	/* Must use GTK_SELECTION_SINGLE for the context-menu to work properly */
	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (history_view), GTK_SELECTION_SINGLE);

	/* Double check that there's a selection */
	if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)), &model, &history_iter))
	{
		/* Fetch the color info from the treeview row */
		gtk_tree_model_get (model, &history_iter,
							COL_HEX,	&shex,
							COL_R,		&sred,
							COL_G,		&sgreen,
							COL_B,		&sblue,
							-1);

		/* Set the labels for the hex-based context-menu items */
		for (i = 0; i < 8; i++)
		{
			action = GTK_ACTION (gtk_builder_get_object(builder, actions[i]));
			g_object_get (G_OBJECT (action), "label", &item_label, NULL);
			g_object_set (G_OBJECT (action), "label", g_strdup_printf (item_label, shex), NULL);
		}
		
		/* Set the RGB items label */
		action = GTK_ACTION (gtk_builder_get_object (builder, "rgb-tocb"));
		g_object_get (G_OBJECT (action), "label", &item_label, NULL);
		g_object_set (G_OBJECT (action), "label", g_strdup_printf (item_label, sred, sgreen, sblue), NULL);

		/* Free resources */
		g_object_unref (G_OBJECT (action));
		g_free (item_label);
		g_free (sred);
		g_free (sgreen);
		g_free (sblue);
		g_free (shex);
	}
	
	/* Return to multiple selection mode */
	gtk_tree_selection_set_mode ( gtk_tree_view_get_selection (history_view), GTK_SELECTION_MULTIPLE );
	
	/* Connect signals for cancel/apply buttons */
	gtk_builder_connect_signals (builder, NULL);	

	/* Display the context-menu */
	gtk_menu_popup (GTK_MENU (gtk_builder_get_object (builder, "history-context-menu")),
		NULL, NULL, NULL, NULL, (event != NULL) ? event->button : 0, gdk_event_get_time((GdkEvent*)event));

	/* Trash builder */
	g_object_unref (G_OBJECT (builder));
}

/*
	Displays context (right-click) menu for selection history
	This function simply checks the button and passes the event to the popup event handler
*/
gboolean popup_history_button (GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
{
	/* single click with the right mouse button? */
	if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3)
	{
		GtkTreeSelection	*selection;
		GtkTreePath			*path;

		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
		if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint) event->x, (gint) event->y, &path, NULL, NULL, NULL))
		{
			return EVENT_UNHANDLED;
		}

		gtk_tree_selection_unselect_all (selection);
		gtk_tree_selection_select_path (selection, path);
		gtk_tree_path_free (path);

		popup_history_menu (treeview, event, userdata);

		return EVENT_HANDLED;
	}

	return EVENT_UNHANDLED;
}

/*
	Make sure we catch Shift+F10 for the context-menu
*/
gboolean popup_history_key (GtkWidget *treeview, gpointer userdata)
{
	popup_history_menu (treeview, NULL, userdata);

	return EVENT_HANDLED;
}

/*
	draws a 2 pixel black and white border using the passed Cairo drawing context
*/
void draw_border (cairo_t *this_ctx, int width, int height)
{
	cairo_rectangle			(this_ctx, 0, 0, width, height);
	cairo_set_source_rgb	(this_ctx, 0, 0, 0);
	cairo_stroke			(this_ctx);
	cairo_rectangle			(this_ctx, 2, 2, width - 4, height - 4);
	cairo_set_source_rgb	(this_ctx, 1, 1, 1);
	cairo_stroke			(this_ctx);
	return;
}

/*
	Global KeyEvent listener
*/
gboolean on_key_up (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
	switch(event->keyval)
	{
		case GDK_Left: /* Move magnifier left */
			x -= color_locked ? 1 : 0;
			return EVENT_HANDLED;
		break;

		case GDK_Up: /* Move magnifier up */
			y -= color_locked ? 1 : 0;
			return EVENT_HANDLED;
		break;

		case GDK_Right: /* Move magnifier right */
			x += color_locked ? 1 : 0;
			return EVENT_HANDLED;
		break;

		case GDK_Down: /* Move magnifier down */
			y += color_locked ? 1 : 0;
			return EVENT_HANDLED;
		break;

		case GDK_space: /* Either toggle locking, or add currently focused color to selection history */
			if(event->state & GDK_CONTROL_MASK)
			{
				color_locked = !color_locked;
				return EVENT_HANDLED;
			}
			/* Just grab a section from the current swatch */
			history_swatch = gdk_pixbuf_get_from_drawable (NULL, swatch->window, NULL, 3, 3, 0 ,0, 20, 20);

			/* grayscale half of the swatch */
			_gray = INTENSITY (_red, _green, _blue);
			gdk_pixbuf_fill (
				gdk_pixbuf_new_subpixbuf (history_swatch, 0, 0, 10, 20),
				(16777216 * _gray) + (65536 * _gray) + (256 * _gray)
			);
			gdk_pixbuf_fill ( gdk_pixbuf_new_subpixbuf (history_swatch, 9, 0, 2, 20), (guint32)4294967295);

			gtk_list_store_prepend(history_store, &history_iter);
			gtk_list_store_set(history_store, &history_iter,
				COL_COLOR, history_swatch,
				COL_HEX, hex->text,
				COL_R, g_strdup_printf ("%i", _red),
				COL_G, g_strdup_printf ("%i", _green),
				COL_B, g_strdup_printf ("%i", _blue),
			-1);
			g_object_unref (history_swatch);
			
	
			/* Limit history to history_len length */
			while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (history_store), &history_iter, NULL, history_len))
			{
				gtk_list_store_remove (history_store, &history_iter);
			}

			return EVENT_HANDLED;
		break;

		case GDK_Delete: /* Remove selected history items */
			remove_history_selections ();
			return EVENT_HANDLED;
		break;

		default:
			return EVENT_UNHANDLED;
		break;
	}
	/* quick reference */
	//GDK_SHIFT_MASK
	//GDK_CONTROL_MASK
	//GDK_ALT_MASK
}

/*
	Forces swatch/magnifier to be redrawn
	mainly for expose events
*/
void force_redraw ()
{
	redraw_forced = TRUE;

	return;
}

/*
	Sets running state to false so main() loop knows to quit,
	connected to destroy signal & File->Quit menuitems activate signal
*/
void quit_running()
{
	save_preferences ();
	running = FALSE;
}

int main (int argc, char **argv)
{
	gtk_init (&argc, &argv);
	
	/* Default preferences */
	always_on_top	= TRUE;
	confirm_clear	= TRUE;
	history_len		= 10;
	
	/* Fetch preferences */
	GKeyFile *prefs	= g_key_file_new();
	if (g_key_file_load_from_file (prefs, g_build_filename (G_DIR_SEPARATOR_S, g_get_home_dir (), ".picksel", "config.ini", NULL), G_KEY_FILE_KEEP_COMMENTS, NULL))
	{
		always_on_top	= g_key_file_get_boolean (prefs, "preferences", "always_on_top", NULL);
		confirm_clear	= g_key_file_get_boolean (prefs, "preferences", "confirm_clear", NULL);
		history_len		= max(1, g_key_file_get_integer (prefs, "preferences", "history_len", NULL));
	}
	g_key_file_free (prefs);

	GtkBuilder			*builder;
	GtkTreeViewColumn	*scol, *hcol, *rcol, *gcol, *bcol;
	GtkCellRenderer		*renderer;
	char		xmlfile[256];
	
	getAbsolutePathForFile(xmlfile, 256, "picksel.xml", 11);
	builder = gtk_builder_new ();
	gtk_builder_add_from_file (builder, xmlfile, NULL);

	/* Get references to widgets that will be used programaticly */
	root_window		= gdk_get_default_root_window ();
	buffer			= gdk_pixmap_new (root_window, 220, 220, -1);
	window			= GTK_WIDGET (gtk_builder_get_object (builder, "window"));
	rgb				= GTK_ENTRY (gtk_builder_get_object (builder, "rgb"));
	hex				= GTK_ENTRY (gtk_builder_get_object (builder, "hex"));
	magnified		= GTK_IMAGE (gtk_builder_get_object (builder, "magnified"));
	swatch			= GTK_WIDGET (gtk_builder_get_object (builder, "swatch"));
	history_view	= GTK_TREE_VIEW (gtk_builder_get_object (builder, "history_view"));

	/*
		Setup selection history
	*/
	history_store	= gtk_list_store_new (NUM_COLS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
	gtk_tree_view_set_model (history_view, GTK_TREE_MODEL (history_store));
	/* mini-swatch column */
	scol = gtk_tree_view_column_new();
	gtk_tree_view_column_set_title(scol, "--");
	renderer = gtk_cell_renderer_pixbuf_new();
	gtk_tree_view_column_pack_start(scol, renderer, FALSE);
	gtk_tree_view_column_set_attributes(scol, renderer,
										"pixbuf", COL_COLOR,
										NULL);
	/* Hexedecimal column */
	hcol = gtk_tree_view_column_new();
	gtk_tree_view_column_set_title(hcol, "Hex");
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(hcol, renderer, TRUE);
	gtk_tree_view_column_set_attributes(hcol, renderer,
										"text", COL_HEX,
										NULL);
	/* Red column*/
	rcol = gtk_tree_view_column_new();
	gtk_tree_view_column_set_title(rcol, "R");
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(rcol, renderer, TRUE);
	gtk_tree_view_column_set_attributes(rcol, renderer,
										"text", COL_R,
										NULL);
	/* Green column */
	gcol = gtk_tree_view_column_new();
	gtk_tree_view_column_set_title(gcol, "G");
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(gcol, renderer, TRUE);
	gtk_tree_view_column_set_attributes(gcol, renderer,
										"text", COL_G,
										NULL);
	/* Blue column */
	bcol = gtk_tree_view_column_new();
	gtk_tree_view_column_set_title(bcol, "B");
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(bcol, renderer, TRUE);
	gtk_tree_view_column_set_attributes(bcol, renderer,
										"text", COL_B,
										NULL);
	/* Add columns to the view */
	gtk_tree_view_append_column(GTK_TREE_VIEW (history_view), scol);
	gtk_tree_view_append_column(GTK_TREE_VIEW (history_view), hcol);
	gtk_tree_view_append_column(GTK_TREE_VIEW (history_view), rcol);
	gtk_tree_view_append_column(GTK_TREE_VIEW (history_view), gcol);
	gtk_tree_view_append_column(GTK_TREE_VIEW (history_view), bcol);
	
	/* 
		Enable multiple row selection
		NOTE: Conflicts with context (right-click) menu and is disabled during context-menu operations
	*/
	gtk_tree_selection_set_mode ( gtk_tree_view_get_selection (history_view), GTK_SELECTION_MULTIPLE );

	/* Drawing contexts for the magnifier & selected color swatch */
	ctx			= gdk_cairo_create(buffer);
	swatch_ctx	= gdk_cairo_create(swatch->window);

	/* Icon overlay pointing to the current color in the magnifier */
	eyedropper = gtk_widget_render_icon (window, GTK_STOCK_COLOR_PICKER, GTK_ICON_SIZE_BUTTON, "");
	eyedropper = gdk_pixbuf_scale_simple (eyedropper, 2 * gdk_pixbuf_get_width (eyedropper), 2 * gdk_pixbuf_get_height (eyedropper), GDK_INTERP_NEAREST);

	/* Connect signals and do away with the builders reference since we have references to what we need */
	gtk_builder_connect_signals (builder, NULL);
	g_object_unref (G_OBJECT (builder));

	/* Set Always on Top, then show application window */
	gtk_window_set_keep_above (GTK_WINDOW (window), always_on_top);
	gtk_widget_show (window);

	/* Main loop */
	running = TRUE;
	while ( running )
	{
		/* Let GTK signals do their thing */
		gtk_main_iteration_do (FALSE);

		/* Only pay attention to the mouse position when the magnifier isn't locked */
		if(!color_locked)
		{
			gdk_window_get_pointer (root_window, &x, &y, &state);
		}

		/* Only redraw the magnifier & current color swatch if the magnifiers position has changed */
		if(redraw_forced || (x != last_x) || (y != last_y))
		{
			redraw_forced = FALSE;
			last_x = x;
			last_y = y;

			gdk_window_get_geometry (root_window, NULL, NULL, &width, &height, NULL);

			left	= max(0, x - 5);
			right	= min(width, x + 6);
			top		= max(0, y - 5);
			bottom	= min(height, y + 6);
			width	= right - left;
			height	= bottom - top;

			section = gdk_drawable_get_image (root_window, left, top, width, height);

			color	= gdk_image_get_pixel(section, width/2, height/2);
			_red	= color >> 16;
			_green	= color >> 8 & 255;
			_blue	= color & 255;

			gtk_entry_set_text(rgb, g_strdup_printf ("rgb(%i, %i, %i)", _red, _green, _blue));
			gtk_entry_set_text(hex, g_strdup_printf ("#%.2X%.2X%.2X", _red, _green, _blue));

			cairo_rectangle (swatch_ctx, 0, 0, swatch->allocation.width, swatch->allocation.height);
			cairo_set_source_rgb (swatch_ctx, (double)_red/255, (double)_green/255, (double)_blue/255);
			cairo_fill (swatch_ctx);
			draw_border (swatch_ctx, swatch->allocation.width, swatch->allocation.height);

			cairo_rectangle (ctx, 0, 0, 220, 220);
			cairo_set_source_rgb (ctx, 0.75, 0.75, 0.75);
			cairo_fill (ctx);

			for(i = 0, xx = 11 - width; i < width; i++, xx++)
			{
				for(j = 0, yy = 11 - height; j < height; j++, yy++)
				{
					color	= gdk_image_get_pixel(section, i, j);
					red		= color >> 16;
					green	= color >> 8 & 255;
					blue	= color & 255;

					cairo_rectangle			(ctx, (20 * xx), (20 * yy), 20, 20);
					cairo_set_source_rgb	(ctx, (double)red/255, (double)green/255, (double)blue/255);
					cairo_fill				(ctx);
				}
			}
			g_object_unref (section);

			/*
				Draw icon overlay, pointing to currently focused pixel,
				then draw black & white border around the edge of magnifier
			*/
			gdk_cairo_set_source_pixbuf (ctx, eyedropper, 105, 115 - gdk_pixbuf_get_height (eyedropper));
			cairo_paint(ctx);
			draw_border	(ctx, 220, 220);

			/* Apply the buffered magnifier to the display widget */
			gtk_image_set_from_pixmap (magnified, buffer, NULL);
		}
		/* Delay next iteration */
		usleep(1000);
	}

	return 0;
}

/*
	TODO: Work persistant selection history into settings file
	TODO: Translate language entries in glade/xml interface files
*/

